package com.lc1993929.shucao.util;

import com.lc1993929.shucao.constants.Constants;
import com.lc1993929.shucao.entity.PosInfo;
import com.lc1993929.shucao.map.Coord;
import com.lc1993929.shucao.map.Node;

import java.util.*;

/**
 * 没有策略的A星算法
 */
public class AStar2 {

    public final static int DIRECT_VALUE = 1; // 横竖移动代价

    // 优先队列(升序)
    private static Queue<Node> openList = new PriorityQueue<Node>();
    private static List<Node> closeList = new ArrayList<Node>();


    public static Integer getLength(Coord start, Coord end) {
        Node startNode = new Node(start);
        openList.clear();
        closeList.clear();

        openList.add(startNode);

        Node endNode = findEndNode(end);

        if (Objects.isNull(endNode)) {
            return 0;
        }

        return getPathLength(endNode);
    }

    private static Integer getPathLength(Node node) {
        Integer length = 0;
        if (Objects.isNull(node)) {
            return length;
        }
        while (true) {
            if (node.getParent() == null) {
                return length;
            }
            //将终点坐标的父坐标变成路径
            node = node.getParent();
            length++;
        }
    }

    private static Node findEndNode(Coord endCoord) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            Node endNode = findNodeInClose(endCoord);
            if (endNode != null) {
                return endNode;
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(current, endCoord);
        }

        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.getH() > 0).min(Comparator.comparingInt(Node::getH));
        if (!optional.isPresent()) {
            return null;
        }

        Node end = optional.get();
        return end;
    }

    private static void addNeighborNodeInOpen(Node current, Coord endCoord) {
        // 左
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX() - 1, current.getCoord().getY()), endCoord);
        // 上
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX(), current.getCoord().getY() - 1), endCoord);
        // 右
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX() + 1, current.getCoord().getY()), endCoord);
        // 下
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX(), current.getCoord().getY() + 1), endCoord);
    }

    private static void addNeighborNodeInOpen(Node current, Coord neighborCoord, Coord endCoord) {
        //如果该点无法加入到open中，则直接忽略
        if (canAddNodeToOpen(neighborCoord)) {
            // 计算邻结点的G值
            int G = current.getG() + DIRECT_VALUE;
            //在open中找到邻结点
            Node child = findNodeInOpen(neighborCoord);
            if (child == null) {
                // 计算H值
                int H = calcH(endCoord, neighborCoord);
                child = new Node(neighborCoord, current, G, H);
                if (neighborCoord.equals(endCoord)) {
                    closeList.add(child);
                }
                openList.add(child);
            } else if (child.getG() > G) {
                child.setG(G);
                child.setParent(current);
                openList.add(child);
            }
        }
    }

    /**
     * 判断结点能否放入Open列表
     */
    private static boolean canAddNodeToOpen(Coord coord) {
        // 是否在地图中
        if (coord.getX() < 0 || coord.getX() >= Constants.playerMap.getColLen() || coord.getY() < 0 || coord.getY() >= Constants.playerMap.getRowLen()) {
            return false;
        }

        PosInfo posInfo = MapUtil.getPosInfoByCoord(coord);
        if (Objects.isNull(posInfo) || posInfo.getBlock()) {
            return false;
        }

        return !isCoordInClose(coord);
    }


    /**
     * 判断坐标是否在close表中
     */
    private static boolean isCoordInClose(Coord coord) {
        if (closeList.isEmpty()) {
            return false;
        }
        return closeList.stream().anyMatch(node -> node.getCoord().equals(coord));
    }

    private static Node findNodeInClose(Coord coord) {
        return closeList.stream().filter(node -> node.getCoord().equals(coord))
                .findAny().orElse(null);
    }

    /**
     * 从Open列表中查找结点
     */
    private static Node findNodeInOpen(Coord coord) {
        return openList.stream().filter(node -> node.getCoord().equals(coord))
                .findAny().orElse(null);
    }

    /**
     * 计算H的估值：“曼哈顿”法，坐标分别取差值相加
     */
    public static int calcH(Coord end, Coord coord) {
        return Math.abs(end.getX() - coord.getX())
                + Math.abs(end.getY() - coord.getY());
    }
}
